import * as THREE from 'three';
import _ from 'lodash';
import Base from './Base.js';
import Point from '../../Point.js';


let DIRECTOR_3D_INSTANCE = null;
class Director extends Base {
    constructor(root, artist) {
        super();
        this.isRunning = false;
        this.timetamp = Date.now();
        this.throttle = 30; // drop down FPS ...
        this.root = root;
        this.delta = 0.01;
        this.artist = artist;

        this.renderer = root.renderer;
        this.viewport = artist.viewport;
        this.camera = artist.camera;
        this.defaultCamera = null;
        this.objects = [];
        this.paintingType = null;
        this.intersectsCache = [];

        this.clock = new THREE.Clock();
        // this.initPointerSprite();
        this.pushScene(artist.getCurrentScene());

        // this.initAnimationMixer(this.currentScene);
        // // this.mixer = new THREE.AnimationMixer(this.currentScene);
        this.mixers = {};
        // this.mixers = [];
        this.mouseDownEvent = {};
        this.isMouseMove = false;
        this.bindEvents();

        DIRECTOR_3D_INSTANCE = this;
    }

    static getInstance(root, artist) {
        if (DIRECTOR_3D_INSTANCE) return DIRECTOR_3D_INSTANCE;
        else return (DIRECTOR_3D_INSTANCE = new Director(root, artist));
    }

    destory() {
        console.log('Director.destory...');
        this.clearAnimationMixer();
        // this.mixers = [];
        this.isRunning = false;
        this.stop();
        this.unbindEvents();
        super.destory();
        this.renderer = null;
    }

    clearAnimationMixer(model, id) {
        this.mixers = {};
    }
    initAnimationMixer(model, id) {
        // console.log('[Director] initAnimationMixer: ', id, this.mixers);
        // this.mixer = new THREE.AnimationMixer(model);
        // return this.mixer;
        if (this.mixers && this.mixers[id]) {
            this.mixers[id].stopAllAction();
            return this.mixers[id];
        }
        this.mixers[id] = new THREE.AnimationMixer(model);
        return this.mixers[id];
    }
    // animationMixerChanged(mixer) {
    //     if (!mixer) return;
    //     this.mixer = mixer;
    // }

    pushScene(scene) {
        // console.log('Director.pushScene: ', scene);
        this.currentScene = scene;
        // this.initPointerSprite();
    }
    getCurrentScene() {
        return this.currentScene;
    }

    topViewChanged(res) {
        if (!this.camera) return;
        if (res) this.camera.position.set(0, 500, 0);
        else this.camera.position.set(this.defaultCamera.x, this.defaultCamera.y, this.defaultCamera.z);
        this.camera.lookAt(0, 0, 0);

        if (!this.controls) return;
        this.controls.target.set(0, 0, 0);
        this.controls.enableRotate = !res;
    }
    rotatableChanged(tag, val) {
        if (tag && val) this.rotationSpeed = val;
        else this.rotationSpeed = 0;
    }

    // events
    bindEvents() {
        this.unbindEvents();

        // this.raycaster = new THREE.Raycaster(this.camera.position, vector.sub(this.camera.position).normalize());
        this.raycaster = new THREE.Raycaster();
        this.raycaster.params.Points.threshold = 0.1;

        if (!this.viewport || !this.viewport.el) return;
        // // this.viewport.el.addEventListener('mousewheel', _.throttle(this.mouseWheelHandler.bind(this), 41.7), false);
        // // this.viewport.el.addEventListener('mousemove', _.throttle(this.pointerMoveHandler.bind(this), 41.7), false);
        this.viewport.el.addEventListener('pointermove', _.throttle(this.pointerMoveHandler.bind(this), 41.7), false);
        // this.viewport.el.addEventListener('pointerdown', _.throttle(this.pointerDownHandler.bind(this), 41.7), false);
        this.viewport.el.addEventListener('click', _.throttle(this.clickHandler.bind(this), 41.7), false);
        this.viewport.el.addEventListener('pointerdown', _.throttle(this.pointerdownHandler.bind(this), 41.7), false);
        this.viewport.el.addEventListener('pointermove', _.throttle(this.pointermoveHandler.bind(this), 41.7), false);
        this.viewport.el.addEventListener('pointerup', _.throttle(this.pointerupHandler.bind(this), 41.7), false);
        // this.viewport.el.addEventListener('mousemove', _.throttle(this.mousemove.bind(this), 41.7), false);
        this.viewport.el.children[0].addEventListener('webglcontextlost', this.webglcontextlostHandler, false);
    }
    unbindEvents() {
        if (!this.viewport || !this.viewport.el) return;
        this.viewport.el.removeEventListener('pointermove', _.throttle(this.pointerMoveHandler.bind(this), 41.7), false); //eslint-disable-line
        // this.viewport.el.removeEventListener('pointerdown', _.throttle(this.pointerDownHandler.bind(this), 41.7), false); //eslint-disable-line
        this.viewport.el.removeEventListener('click', _.throttle(this.clickHandler.bind(this), 41.7), false); //eslint-disable-line
        this.viewport.el.removeEventListener('pointerdown', _.throttle(this.pointerdownHandler.bind(this), 41.7), false); //eslint-disable-line
        this.viewport.el.removeEventListener('pointermove', _.throttle(this.pointermoveHandler.bind(this), 41.7), false); //eslint-disable-line
        this.viewport.el.removeEventListener('pointerup', _.throttle(this.pointerupHandler.bind(this), 41.7), false); //eslint-disable-line
        this.viewport.el.children[0].removeEventListener('webglcontextlost', this.webglcontextlostHandler, false);
    }
    webglcontextlostHandler() {
        console.warn('Director.webglcontextlostHandler...');
        // if (this.running) cancelAnimationFrame(this.running);
        // setTimeout(() => window.location.reload(), 0);
        // setTimeout(() => {
        //     window.location.replace(window.location.href);
        // }, 0);
    }
    mouseWheelHandler(e) {
        let factor = 15;
        let p = Point.getInstanceByMouseOffset(e, e.target.domElement);
        let px = (p.x / this.viewport.width) * 2 - 1,
            py = -(p.y / this.viewport.height) * 2 + 1;
        // let px = (e.clientX / this.controls.domElement.width) * 2 - 1,
        //     py = -(e.clientY / this.controls.domElement.height) * 2 + 1;
        let vector = new THREE.Vector3(px, py, 0);
        vector.unproject(this.controls.object);
        vector.sub(this.controls.object.position).setLength(factor);
        if (e.deltaY < 0) {
            this.controls.object.position.add(vector);
            this.controls.target.add(vector);
        } else {
            this.controls.object.position.sub(vector);
            this.controls.target.sub(vector);
        }
        this.controls.update();
    }
    pointerdownHandler(e) {
        // console.log('Director.pointerdownHandler:', e);
        this.isMouseMove = false;
        this.mouseDownEvent = e;
    }
    pointerupHandler(e) {
        // console.log('Director.pointerupHandler: ', e);
        // this.isMouseMove = false;
        if (
            Math.abs(e.clientX - this.mouseDownEvent.clientX) > 10 ||
            Math.abs(e.clientY - this.mouseDownEvent.clientY) > 10
        ) {
            this.isMouseMove = true;
            this.mouseDownEvent = {};
        }
    }

    pointermoveHandler(e) {
        // console.log('Director.pointermoveHandler:', e);
        // this.isMouseMove = true;
    }
    clickHandler(e) {
        if (!this.viewport || this.isMouseMove) return;
        let p = Point.getInstanceByMouseOffset(e);
        let px = (p.x / this.viewport.width) * 2 - 1,
            py = -(p.y / this.viewport.height) * 2 + 1;
        let intersects = this.getIntersectObjects(px, py);
        if (intersects && intersects.length && typeof this.root.clickHandler === 'function') {
            this.root.clickHandler(intersects[0].object, intersects[0].instanceId);
        } else {
            if (typeof this.root.clickHandler === 'function') this.root.clickHandler('empty', 'e');
        }
    }
    pointerMoveHandler(e) {
        if (!this.viewport) return;
        e.preventDefault();
        let p = Point.getInstanceByMouseOffset(e);
        let px = (p.x / this.viewport.width) * 2 - 1,
            py = -(p.y / this.viewport.height) * 2 + 1;
        if (this.artist.outlinePass) {
            let intersects = this.getIntersectObjects(px, py);
            let obj = intersects && intersects.length ? intersects[0].object : null;
            if (typeof this.root.pointerMoveHandler === 'function') this.root.pointerMoveHandler(obj);
        }

        // console.log('===> camera: ', this.camera.position, ', scene.rotation: ', this.currentScene.rotation);
        // console.log('===> camera: ', this.camera, ', scene: ', this.currentScene);
        // console.log('===> camera: ', this.camera.position, ', rotation: ', this.camera.rotation);

        // // console.log('Director.pointerMoveHandler: ', this.objects, intersects, p.x, p.y, px, py);
        // // console.log('Director.pointerMoveHandler: ', vector.x * 1000, vector.y * 1000, vector.z);
        // if (intersects && intersects.length) {
        //     let intersect = intersects[0];
        //     let x = intersect.point.x,
        //         y = -intersect.point.z;
        //     // if (x > 0.1)
        //     this.root.viewport.pointer.x = x * 1000;
        //     this.root.viewport.pointer.y = y * 1000;
        //     console.log('imtersrct => ' + intersect, intersect.point);
        // }
    }
    getIntersectObjects(x, y) {
        // if (!this.artist.outlinePass) return;
        let mp = new THREE.Vector2(x, y);
        this.raycaster.setFromCamera(mp, this.camera);
        if (!this.modelsMesh || !this.modelsMesh.length)
            this.modelsMesh = typeof this.root.getIntersectMesh === 'function' ? this.root.getIntersectMesh() : [];
        if (!this.modelsMesh || !this.modelsMesh.length) return [];
        return this.raycaster.intersectObjects(this.modelsMesh);
    }
    getCacheGroup(obj) {
        if (!this._groupCache) this._groupCache = {};
        if (this._groupCache[obj.uuid]) return this._groupCache[obj.uuid];
        let curGroup = null;
        this.modelsGroup.traverse(o => {
            if (o.children.includes(obj) === true) curGroup = o;
        });
        this._groupCache[obj.uuid] = curGroup;
        return curGroup;
    }
    getCacheMeshs(obj) {
        if (!this._meshCache) this._meshCache = {};
        if (this._meshCache[obj.uuid]) return this._meshCache[obj.uuid];
        let curGroup = this.getCacheGroup(obj);
        if (!curGroup) return null;
        let curMeshs = [];
        curGroup.traverse(o => {
            if (o.isMesh) curMeshs.push(o);
        });
        this._meshCache[obj.uuid] = curMeshs;
        return curMeshs;
    }
    // resetCaches() {
    //     // console.log('---> resetCaches');
    //     this._groupCache = {};
    //     this._meshCache = {};
    //     this.modelsGroup = null;
    //     this.modelsMesh = null;
    // }

    // lifecycle
    start() {
        // console.log('Director.start: ', this);
        this.stop();
        this.isRunning = true;
        this.timetamp = Date.now();
        this.run.call(this);
        // this.timeRender();
    }
    run() {
        if (!this.isRunning) return;
        if (!this.root.renderer) return;
        if (Date.now() - this.timetamp < this.throttle)
            return (this.running = requestAnimationFrame(this.run.bind(this)));
        // this.stop();
        // console.log('Director.run: ', this);

        if (this.mixer) {
            let mixerUpdateDelta = this.clock.getDelta();
            this.mixer.update(mixerUpdateDelta);
            // console.log('Director.run: ', this.mixer, mixerUpdateDelta);
        }
        // if (this.mixers && this.mixers.length) {
        //     let mixerUpdateDelta = this.clock.getDelta();
        //     for (let i = 0, len = this.mixers.length; i < len; i++) {
        //         this.mixers[i].update(mixerUpdateDelta);
        //     }
        //     // console.log('===>      ', this.mixers, mixerUpdateDelta);
        // }
        if (this.mixers && Object.keys(this.mixers).length) {
            let mixerUpdateDelta = this.clock.getDelta();
            // Object.keys(this.mixers).forEach(m => this.mixers[m].update(mixerUpdateDelta));
            Object.keys(this.mixers).forEach(m => this.mixers[m].update(mixerUpdateDelta));
            // console.log('===>      ', Object.keys(this.mixers), mixerUpdateDelta);
        }

        if (this.artist.stats) this.artist.stats.update();

        // // if (this.rotatable) this.scene.rotation.y += 0.001;
        // if (this.settings['rotation speed']) this.scene.rotation.y += this.settings['rotation speed'];
        // console.log(this.rotationSpeed);
        if (this.rotationSpeed) this.currentScene.rotation.y += this.rotationSpeed;
        // this.currentScene.rotation.y += 0.05;
        if (this.artist.controls) this.artist.controls.update();
        if (this.artist.shadowCameraHelper) this.artist.shadowCameraHelper.update();

        // // this.root.renderer.clear();
        // this.root.renderer.render(this.currentScene, this.camera);
        // this.root.renderer.render(this.artist.currentScene, this.artist.camera);
        // // this.root.renderer.clearDepth();

        // // if (this.renderEnabled) {
        // //     if (this.controls) this.controls.update();
        // //     this.root.renderer.clear();
        // //     this.root.renderer.render(this.scene, this.camera);
        // //     this.root.renderer.clearDepth();
        // // }

        // if (this.effect) this.effect.render(this.artist.currentScene, this.artist.camera);
        // if (this.composer) this.composer.render();
        // 缩放边界限定
        // if (this.artist.camera.zoom > 3) this.artist.camera.zoom = 3;
        // if (this.artist.camera.zoom < 0.5) this.artist.camera.zoom = 0.5;

        if (this.artist.composer) {
            this.artist.composer.render(this.delta);
            // this.artist.composer.clearDepth();
        } else {
            this.root.renderer.render(this.artist.currentScene, this.artist.camera);
        }
        this.root.renderer.clearDepth();

        if (this.root && typeof this.root.running === 'function') this.root.running();
        if (this.artist && typeof this.artist.running === 'function') this.artist.running();

        this.timetamp = Date.now();
        this.running = requestAnimationFrame(this.run.bind(this));
    }
    timeRender(t = 1000) {
        this.renderEnabled = true;
        if (this.timeout) clearTimeout(this.timeout);
        this.timeout = setTimeout(() => {
            this.renderEnabled = false;
        }, t);
    }
    stop() {
        this.isRunning = false;
        // console.log('Director.stop ...', this.running);
        if (this.running) cancelAnimationFrame(this.running);
    }

    // painter
    handlePainting(args) {
        this.paintingType = args;
        console.log('Director.handlePainting: ', args);
    }
}

export default Director;
